/*
 * Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <sys/epoll.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <linux/virtio_net.h>
#include <sys/types.h>
#include <dirent.h>

#include "virtnet.h"

/***************** virtio-net *******************/
char *feature_names[] = {
	[VIRTIO_NET_F_CSUM]		= "VIRTIO_NET_F_CSUM",
	[VIRTIO_NET_F_GUEST_CSUM]	= "VIRTIO_NET_F_GUEST_CSUM",
	[VIRTIO_NET_F_CTRL_GUEST_OFFLOADS] = "VIRTIO_NET_F_CTRL_GUEST_OFFLOADS",
	[VIRTIO_NET_F_MTU]		= "VIRTIO_NET_F_MTU",
	[VIRTIO_NET_F_GSO]		= "VIRTIO_NET_F_GSO",
	[VIRTIO_NET_F_MAC]		= "VIRTIO_NET_F_MAC",
	[VIRTIO_NET_F_GUEST_TSO4]	= "VIRTIO_NET_F_GUEST_TSO4",
	[VIRTIO_NET_F_GUEST_TSO6]	= "VIRTIO_NET_F_GUEST_TSO6",
	[VIRTIO_NET_F_GUEST_ECN]	= "VIRTIO_NET_F_GUEST_ECN",
	[VIRTIO_NET_F_GUEST_UFO]	= "VIRTIO_NET_F_GUEST_UFO",
	[VIRTIO_NET_F_HOST_TSO4]	= "VIRTIO_NET_F_HOST_TSO4",
	[VIRTIO_NET_F_HOST_TSO6]	= "VIRTIO_NET_F_HOST_TSO6",
	[VIRTIO_NET_F_HOST_ECN]		= "VIRTIO_NET_F_HOST_ECN",
	[VIRTIO_NET_F_HOST_UFO]		= "VIRTIO_NET_F_HOST_UFO",
	[VIRTIO_NET_F_MRG_RXBUF]	= "VIRTIO_NET_F_MRG_RXBUF",
	[VIRTIO_NET_F_STATUS]		= "VIRTIO_NET_F_STATUS",
	[VIRTIO_NET_F_CTRL_VQ]		= "VIRTIO_NET_F_CTRL_VQ",
	[VIRTIO_NET_F_CTRL_RX]		= "VIRTIO_NET_F_CTRL_RX",
	[VIRTIO_NET_F_CTRL_VLAN]	= "VIRTIO_NET_F_CTRL_VLAN",
	[VIRTIO_NET_F_CTRL_RX_EXTRA]	= "VIRTIO_NET_F_CTRL_RX_EXTRA",
	[VIRTIO_NET_F_GUEST_ANNOUNCE]	= "VIRTIO_NET_F_GUEST_ANNOUNCE",
	[VIRTIO_NET_F_MQ]		= "VIRTIO_NET_F_MQ",
	[VIRTIO_NET_F_CTRL_MAC_ADDR]	= "VIRTIO_NET_F_CTRL_MAC_ADDR",
	[VIRTIO_NET_F_STANDBY]		= "VIRTIO_NET_F_STANDBY",
	[VIRTIO_NET_F_SPEED_DUPLEX]	= "VIRTIO_NET_F_SPEED_DUPLEX",
	[VIRTIO_F_NOTIFY_ON_EMPTY]	= "VIRTIO_F_NOTIFY_ON_EMPTY",
	[VIRTIO_F_ANY_LAYOUT]		= "VIRTIO_F_ANY_LAYOUT",
	[VIRTIO_F_VERSION_1]		= "VIRTIO_F_VERSION_1",
	[VIRTIO_F_IOMMU_PLATFORM]	= "VIRTIO_F_IOMMU_PLATFORM",
	[VIRTIO_F_RING_PACKED]		= "VIRTIO_F_RING_PACKED",
	[VIRTIO_F_ORDER_PLATFORM]	= "VIRTIO_F_ORDER_PLATFORM",
	[VIRTIO_F_SR_IOV]		= "VIRTIO_F_SR_IOV",
};

void virtnet_log_open(void)
{
	openlog("virtio-net-controller",
		LOG_CONS | LOG_PID | LOG_NDELAY, LOG_USER);
}

void virtnet_log_close(void)
{
	closelog();
}

void virtnet_set_log_level(int level)
{
	setlogmask(LOG_UPTO(level));
}

int status_id[STATUS_ID_SIZE] = {
		STATUS_ACKNOWLEDGE	  ,
		STATUS_DRIVER		  ,
		STATUS_DRIVER_OK	  ,
		STATUS_FEATURES_OK	  ,
		STATUS_DEVICE_NEEDS_RESET ,
		STATUS_FAILED		  ,
};

char *status_names[] = {
		"STATUS_ACKNOWLEDGE",
		"STATUS_DRIVER",
		"STATUS_DRIVER_OK",
		"STATUS_FEATURES_OK",
		"STATUS_DEVICE_NEEDS_RESET",
		"STATUS_FAILED",
};

/***************** ifreq *******************/

/**
 * Perform ifreq ioctl() on associated Ethernet device.
 *
 * @param[in] dev
 *   Pointer to device name.
 * @param req
 *   Request number to pass to ioctl().
 * @param[out] ifr
 *   Interface request structure output buffer.
 *
 * @return
 *   0 on success, a negative errno value otherwise and rte_errno is set.
 */
static int
virtnet_ifreq(const char *dev, int req, struct ifreq *ifr)
{
	int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
	int ret;

	if (sock == -1)
		return -errno;
	snprintf(ifr->ifr_name, IFNAMSIZ, "%s", dev);
	ret = ioctl(sock, req, ifr);
	if (ret != -1)
		ret = 0;
	close(sock);
	return ret;
}

#define HW_MTU_LL_OVERHEAD (6 + 6 + 2 + 2 + 2 + 4)

/**
 * Get device MTU.
 *
 * @param ctx
 *   Pointer to ibv_context.
 * @param[out] mtu
 *   MTU value output buffer.
 *   MTU returned here does not include link layer overheads, which includes
 *   length of src mac, dst mac, ether type, vlan header, crc sum to 22 bytes.
 *
 * @return
 *   0 on success, a negative errno value otherwise and rte_errno is set.
 */
int
virtnet_get_mtu(struct ibv_context *ctx, uint16_t *mtu)
{
	uint8_t out[DEVX_ST_SZ_BYTES(query_nic_vport_context_out)] = {0};
	uint8_t in[DEVX_ST_SZ_BYTES(query_nic_vport_context_in)] = {0};
	int ret;

	/* port may be ethernet but still have roce disabled */
	DEVX_SET(query_nic_vport_context_in, in, opcode,
		 MLX5_CMD_OP_QUERY_NIC_VPORT_CONTEXT);
	ret = mlx5dv_devx_general_cmd(ctx, in, sizeof(in), out, sizeof(out));
	if (ret)
		return ret;
	*mtu = DEVX_GET(query_nic_vport_context_out, out, nic_vport_context.mtu);
	if (*mtu > HW_MTU_LL_OVERHEAD) {
		*mtu = *mtu - HW_MTU_LL_OVERHEAD;
	} else {
		*mtu = 0;
		ret = -EINVAL;
	}
	return ret;
}

/**
 * Set device MTU.
 *
 * @param dev
 *   Pointer to Ethernet device.
 * @param mtu
 *   MTU value to set.
 *
 * @return
 *   0 on success, a negative errno value otherwise and rte_errno is set.
 */
int
virtnet_set_mtu(const char *dev, uint16_t mtu)
{
	struct ifreq request = { .ifr_mtu = mtu, };

	return virtnet_ifreq(dev, SIOCSIFMTU, &request);
}

/***************** epoll *******************/
int epfd; /* epoll FD. */

/**
 * Close global epoll.
 *
 * return
 * 	0 on success, error otherwise.
 */
int virtnet_epoll_close(void)
{
	int ret = 0;
	if (epfd > 0) {
		ret = close(epfd);
		epfd = 0;
	}
	return ret;
}

/**
 * Initialize global epoll.
 * @param[in] n
 * 	Number of file descriptor source
 * return
 * 	0 on success, error otherwise.
 */
int virtnet_epoll_init(int n)
{
	epfd = epoll_create(n + 1);
	if (epfd < 0) {
		log_error("failed to create epoll");
		return epfd;
	}
	return 0;
}

/**
 * Remove file descriptor from global epoll.
 * @param[in] fd
 * 	file descriptor
 * return
 * 	0 on success, error otherwise.
 */
int virtnet_epoll_del(int fd)
{
	return epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}

/**
 * Enroll file descriptor to global epoll.
 * @param[in] fd
 * 	file descriptor
 * @param[in] data
 * 	callback data
 * return
 * 	0 on success, error otherwise.
 */
int virtnet_epoll_add(int fd, struct virtnet_epoll_data *data)
{
	struct epoll_event event;
	int ret;
	int flags;

	flags = fcntl(fd, F_GETFL);
	ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
	err_if(ret, "failed to change file descriptor to async");

	event.events = EPOLLIN | EPOLLPRI | EPOLLRDHUP | EPOLLHUP;
	event.data.ptr = data;
	ret =  epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
	err_if(ret, "failed to register epoll");

free_exit:
	return ret;
}

/**
 * Wait global epoll with timeout
 * @param[in] timeout_ms
 * 	timeout time in milliseconds.
 * return
 * 	0 on success, error otherwise.
 */
int virtnet_epoll_wait(int timeout_ms)
{
	struct epoll_event event;
	struct virtnet_epoll_data *data;
	int ret;

	ret = epoll_wait(epfd, &event, 1, timeout_ms);
	if (errno == EINTR)
		return 0;
	if (ret < 0)
		log_error("interrupt polling error");
	if (ret <= 0)
		return ret;

	data = event.data.ptr;
	data->cb(data->ctx);

	return 0;
}

struct ibv_device *virtnet_get_ib_dev(const char *ib_name)
{
	struct ibv_device **list;
	int i, dev_count;

	list = ibv_get_device_list(&dev_count);

	if (!list) {
		log_error("Failed to get IB device list");
		return NULL;
	}

	for (i = 0; i < dev_count; i++) {
		if (!strcmp(list[i]->name, ib_name))
			return list[i];
	}

	return NULL;
}

int virtnet_ibdev_to_pci_addr(const struct ibv_device *ibdev,
			      char *pci_addr_str)
{
	char dirname[PATH_MAX] = {0};
	struct dirent *pci_e;
	struct dirent *ib_e;
	int ret = -ENOENT;
	DIR *pci_dir;
	DIR *ib_dir;

	pci_dir = opendir("/sys/bus/pci/devices");
	if (pci_dir == NULL) {
		log_error("Can't find pci");
		return ret;
	}

	while ((pci_e = readdir(pci_dir)) != NULL) {
		if (pci_e->d_name[0] == '.')
			continue;
		snprintf(dirname, sizeof(dirname),
			 "/sys/bus/pci/devices/%s/infiniband/",
			 pci_e->d_name);
		ib_dir = opendir(dirname);
		if (!ib_dir)
			continue;
		while ((ib_e = readdir(ib_dir)) != NULL) {
			if (ib_e->d_name[0] == '.')
				continue;
			if (!strcmp(ib_e->d_name, ibdev->name)) {
				/* Found. */
				ret = 0;
				strncpy(pci_addr_str, pci_e->d_name,
					PCI_ADDR_STR_LEN);
				log_debug("IB dev[%s] PCI addr[%s]",
					  ibdev->name, pci_addr_str);
				break;
			}
		}
		closedir(ib_dir);
		if (!ret)
			break;
	}
	closedir(pci_dir);

	return ret;
}

int virtnet_get_pci_function(const char *str, uint8_t *num)
{
	char num_char = 0;

	while(*str) {
		num_char = *str;
		str++;
		if (*str == 0)
			break;
	}

	if (num_char >= '0' && num_char <= '9') {
		*num = atoi(&num_char);
		log_debug("PCI function [%d]", *num);
		return 0;
	}

	return -1;
}

struct virtnet_port_ctx *
virtnet_ibdev_to_ibport(const struct virtnet_context *ctx,
			const struct ibv_device *ibdev)
{
	int i;

	for (i = 0; i < IB_NUM_PORTS; i++) {
		if (!ctx->port_ctx[i])
			continue;
		if (!strcmp(ibdev->name, ctx->port_ctx[i]->dev->name))
			return ctx->port_ctx[i];
	}

	return NULL;
}

void virtnet_get_bdf(const struct virtnet_device *dev, char *str, uint16_t len)
{
	memset(str, 0, len);
	snprintf(str, len, "%02x:%02x.%x",
		 dev->snap_pci->pci_bdf.bdf.bus,
		 dev->snap_pci->pci_bdf.bdf.device,
		 dev->snap_pci->pci_bdf.bdf.function);
}

void virtnet_get_mac(const struct virtnet_device *dev, char *str, uint16_t len)
{
	memset(str, 0, len);
	snprintf(str, len, "%02X:%02X:%02X:%02X:%02X:%02X",
		 (uint8_t)((dev->registers->mac >> 40) & 0xff),
		 (uint8_t)((dev->registers->mac >> 32) & 0xff),
		 (uint8_t)((dev->registers->mac >> 24) & 0xff),
		 (uint8_t)((dev->registers->mac >> 16) & 0xff),
		 (uint8_t)((dev->registers->mac >> 8) & 0xff),
		 (uint8_t)((dev->registers->mac >> 0) & 0xff));
}
